/*
 *  +----------------------------------------------------------------------
 *  | sophon [ A FAST GAME FRAMEWORK ]
 *  +----------------------------------------------------------------------
 *  | Copyright (c) 2023-2029 All rights reserved.
 *  +----------------------------------------------------------------------
 *  | Licensed ( http:www.apache.org/licenses/LICENSE-2.0 )
 *  +----------------------------------------------------------------------
 *  | Author: jqiris <1920624985@qq.com>
 *  +----------------------------------------------------------------------
 */

use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::sync::RwLock;

use anyhow::Result;
use chrono::prelude::*;
use chrono::Days;
use chrono::Local;
use colorful::Colorful;
use once_cell::sync::{Lazy, OnceCell};

pub use common::*;
pub use item::*;
pub use writer::*;

mod common;
mod item;
mod writer;

static WRITER: Lazy<Writer> = Lazy::new(|| Writer::new());
static mut LOGGER: OnceCell<Logger> = OnceCell::new();
static LOG_SUFFIX: &'static str = ".log";

pub type Reporter = fn(ctx: String);

type LoggerOption = Box<dyn FnMut(&mut Logger)>;

pub fn with_log_level(level: String) -> LoggerOption {
    Box::new(move |l: &mut Logger| {
        let log_level = str_to_level(&level);
        l.log_level = log_level;
    })
}

pub fn with_log_runtime(runtime: bool) -> LoggerOption {
    Box::new(move |l: &mut Logger| {
        l.log_runtime = runtime;
    })
}

pub fn with_time_format(fmt: String) -> LoggerOption {
    Box::new(move |l: &mut Logger| {
        l.time_format = fmt.to_owned();
    })
}

pub fn with_reporter(reporter: Reporter) -> LoggerOption {
    Box::new(move |l: &mut Logger| l.reporter = Some(reporter))
}
/*------------------writer-------------*/
pub fn with_out_type(typ: String) -> LoggerOption {
    Box::new(move |l: &mut Logger| {
        l.out_type = str_to_out_type(&typ);
    })
}

pub fn with_log_dir(dir: String) -> LoggerOption {
    Box::new(move |l: &mut Logger| l.log_dir = dir.to_owned())
}

pub fn with_log_name(name: String) -> LoggerOption {
    Box::new(move |l: &mut Logger| l.log_name = name.to_owned())
}

pub fn with_log_dump(dump: bool) -> LoggerOption {
    Box::new(move |l: &mut Logger| l.log_dump = dump)
}

pub fn with_std_color(std_color: bool) -> LoggerOption {
    Box::new(move |l: &mut Logger| l.std_color = std_color)
}

#[derive(Debug)]
pub struct Logger {
    log_level: LogLevel,
    log_runtime: bool,
    time_format: String,
    pub reporter: Option<Reporter>,
    log_suffix: String,
    out_type: OutType,
    log_dir: String,
    log_name: String,
    log_dump: bool,
    //是否转储
    dump_date: Option<i64>,
    //转储日期
    std_color: bool,
    //是否标准输出显示彩色
    log_file: Option<RwLock<File>>,
}

impl Logger {
    pub fn global() -> &'static Logger {
        unsafe { LOGGER.get().expect("logger is not initialized") }
    }
    fn global_mut() -> &'static mut Logger {
        unsafe { LOGGER.get_mut().expect("logger is not initialized") }
    }
    pub fn set_logger(l: Logger) {
        unsafe {
            LOGGER.set(l).unwrap();
        }
    }
    pub fn new_log_item(&self, level: LogLevel, txt: String) -> LogItem {
        let item = LogItem {
            log_level: level,
            allow_level: self.log_level.clone(),
            log_runtime: self.log_runtime.clone(),
            time_format: self.time_format.clone(),
            log_time: Local::now(),
            log_loc: None,
            log_txt: txt,
            log_suffix: self.log_suffix.clone(),
        };
        item
    }
    pub fn new(options: Option<Vec<LoggerOption>>) -> Self {
        let mut l = Logger {
            log_level: LogLevel::DEBUG,
            log_runtime: false,
            time_format: "%Y-%m-%d %H:%M:%S".into(),
            reporter: None,
            log_suffix: "".into(),
            out_type: OutType::OutAll,
            log_dir: "logs".to_string(),
            log_name: "log.log".to_string(),
            log_dump: false,
            dump_date: None,
            std_color: true,
            log_file: None,
        };
        if let Some(mut ops) = options {
            for option in ops.iter_mut() {
                option(&mut l);
            }
        }
        //init dump date
        l.next_dump_date();
        //init file handler
        if let Err(err) = l.create_file_handler() {
            eprint!("log create_file_handler err:{}", err);
        }
        l
    }

    fn out_check(&mut self) {
        //check dump date
        if self.out_type > OutType::OutStd && self.log_dump {
            let dump_date = self.dump_date.unwrap();
            let now_date = Local::now().timestamp();
            if dump_date > now_date {
                return;
            }
            //init dump date
            self.next_dump_date();
            //init file handler
            if let Err(err) = self.create_file_handler() {
                eprint!("log create_file_handler err:{}", err);
            }
        }
    }

    fn next_dump_date(&mut self) {
        if self.out_type > OutType::OutStd && self.log_dump {
            let now = Local::now();
            let next = now.checked_add_days(Days::new(1)).unwrap();
            let next = Local
                .with_ymd_and_hms(next.year(), next.month(), next.day(), 0, 0, 0)
                .unwrap();
            self.dump_date = Some(next.timestamp());
        } else {
            self.dump_date = None;
        }
    }

    fn out_std(&self, level: LogLevel, txt: String) {
        if self.std_color {
            let color = level_to_color(level);
            println!("{}", txt.color(color));
        } else {
            println!("{}", txt);
        }
    }

    fn out_file(&self, level: LogLevel, txt: String) -> Result<()> {
        if let Some(log_file) = &self.log_file {
            let mut file = (*log_file).write().unwrap();
            let txt = txt + "\n";
            file.write_all(txt.as_bytes())?;
        } else if self.out_type == OutType::OutFile {
            self.out_std(level, txt);
        }
        Ok(())
    }
    pub fn get_log_file(&self) -> String {
        let now = Local::now();
        let now_date = now.format("%Y%m%d").to_string();
        let mut log_name = self
            .log_name
            .clone()
            .trim_end_matches(LOG_SUFFIX)
            .to_string();
        if self.out_type > OutType::OutStd && self.log_dump {
            log_name = log_name + "_" + &now_date;
        }
        log_name = log_name + LOG_SUFFIX;
        Path::new(&self.log_dir)
            .join(log_name)
            .display()
            .to_string()
    }
    pub fn create_file_handler(&mut self) -> Result<()> {
        if self.out_type > OutType::OutStd {
            fs::create_dir_all(&self.log_dir)?;
            let log_file = self.get_log_file();
            let file = fs::OpenOptions::new()
                .create(true)
                .write(true)
                .append(true)
                .open(log_file)?;
            match &self.log_file {
                Some(handler) => {
                    let _ = *handler.write().unwrap();
                    self.log_file = Some(RwLock::new(file));
                }
                None => {
                    self.log_file = Some(RwLock::new(file));
                }
            }
        } else {
            match &self.log_file {
                Some(handler) => {
                    let _ = *handler.write().unwrap();
                    self.log_file = None;
                }
                None => {
                    self.log_file = None;
                }
            }
        }
        Ok(())
    }
}

pub fn print_runtime(log_level: LogLevel) -> bool {
    let l = Logger::global();
    return l.log_runtime || log_level <= LogLevel::ERROR;
}

pub fn log_macros(log_level: LogLevel, txt: String, loc: Option<LogLocation>) {
    let l = Logger::global();
    let mut item = l.new_log_item(log_level, txt);
    if let Some(lc) = loc {
        item.set_log_loc(lc);
    }
    if log_level == LogLevel::REPORT {
        //发送消息出去
        if let Some(reporter) = &l.reporter {
            let content = item.log_format();
            reporter(content);
        }
    }
    WRITER.send(item);
}
